/*
 * parser.cpp
 *
 *  Created on: 20.12.2010
 *      Author: Dmitry Bratus
 */

#include "rbatch_parser.hpp"
#include "rstml_exception.hpp"

#include <parser.hpp>
#include <stml_exception.hpp>

using namespace stml;
using namespace std;

void rbatch_parser_state_free(void* state) {
    RBatchParserState* parser_state = ((RBatchParserState*)state);

    if (parser_state->buffer) {
        free(parser_state->buffer);
    }
    delete parser_state;
}

VALUE rbatch_parser_class_new(VALUE klass) {
    RBatchParserState* state = new RBatchParserState;

    state->buffer = NULL;
    state->len = state->capacity = 0;

    VALUE obj = Data_Wrap_Struct(klass, NULL, rbatch_parser_state_free, state);
    rb_obj_call_init(obj, 0, NULL);

    return obj;
}

VALUE rbatch_parser_initialize(VALUE self) {
    return self;
}

VALUE rbatch_parser_append_stml(VALUE self, VALUE stml_string) {
    RBatchParserState* state;
    Data_Get_Struct(self, RBatchParserState, state);

    size_t len = RSTRING_LEN(stml_string);
    char* stml_chars = StringValuePtr(stml_string);

    size_t new_len = state->len + len;
    size_t capacity_required = new_len + 1;
    if (capacity_required > state->capacity) {
        size_t new_capacity;
        if (state->buffer) {
            new_capacity = max(state->capacity * 2, capacity_required);
            REALLOC_N(state->buffer, char, new_capacity);
        }
        else {
            new_capacity = max(DEFAULT_BUFFER_CAPACITY_CHARS, capacity_required);
            state->buffer = ALLOC_N(char, new_capacity);
        }
        state->capacity = new_capacity;
    }

    MEMCPY(state->buffer + state->len, stml_chars, char, len);
    state->buffer[new_len] = '\0';
    state->len = new_len;

    return self;
}

VALUE rbatch_parser_generate(VALUE self, VALUE generator) {
    RBatchParserState* state;
    Data_Get_Struct(self, RBatchParserState, state);

    if (!state->buffer) {
        return rb_str_new2("");
    }

    state->in.reset(new stringstream);
    *(state->in) << state->buffer;

    free(state->buffer);
    state->buffer = NULL;
    state->len = state->capacity = 0;

    state->out.reset(new stringstream);

    try {
        state->parser.reset(new Parser(GENERATOR_HTML));
        state->parser->parse(*(state->in), *(state->out));
    }
    catch (StmlException& ex) {
        state->in.release();
        state->parser.release();
        state->out.release();

        VALUE rb_ex = rb_funcall(
            get_rstml_exception_class(),
            rb_intern("new"),
            3,
            rb_str_new2("STML error"),
            INT2FIX((int)ex.get_code()),
            INT2FIX(ex.get_line_no())
        );

        rb_funcall(rb_mKernel, rb_intern("raise"), 1, rb_ex);
        return Qnil;
    }

    state->in.release();
    state->parser.release();

    VALUE result = rb_str_new2(state->out->str().c_str());

    state->out.release();

    return result;
}
